<?php
/**
 * Directory list (model)
 * @author Erko Risthein <erko@risthein.ee>
 * @copyright Copyright (C) 2009 Erko Risthein
 * @link http://erko.risthein.ee/decode/
 * @license http://www.gnu.org/licenses/ GNU General Public License
 * @package decode_explorer
 */
class DirList {
	private $path;
	private $folders = array();
	private $files = array();
	private $total_objects = 0;
	private $total_size = 0;
	private $sorted_by;
	private $sorted_in;
	private $name_sort_url;
	private $size_sort_url;
	private $date_sort_url;
	private $parent_folder;
	private $breadcrumb;
	
	/**
	 * Initializes the DirList
	 * @param string $path to the directory
	 * @param string $sort_by name/size/date
	 * @param string $sort_in asc/desc
	 * @return boolean false, if the given path is not a directory
	 */
	function __construct($path, $sort_by, $sort_in) {
		if (is_dir($path)) {
			$this->path = $path;
			$this->parent_folder = $this->obtain_parent_folder($path);
			$this->read_dir($path);
			Filesystem::format_filesize($this->total_size);
			$this->sort($sort_by, $sort_in);
			$this->sorted_by = $sort_by;
			$this->sorted_in = $sort_in;
			$this->build_sorting_urls($sort_by, $sort_in, $path);
			$this->breadcrumb = $this->build_breadcrumb($path);
		} else {
			return false;
		}
	}
	
	/**
	 * Obtains the parent folder path of the given directory
	 * @param string $path to the directory
	 * @return string parent folder path
	 */
	private function obtain_parent_folder($path) {
		$path = Helper::remove_rootpath($path);
		$path_array = explode('/', $path);
		array_pop($path_array);
		$path = implode('/', $path_array);
		$path = empty($path) ? '.' : $path;
		return Helper::format_url($path);
	}
	
	/**
	 * Iterates through the directory, creates new File and Folder objects and calculates the total number of objects and the total size of the files
	 * @param string $path to the directory
	 */
	private function read_dir($path) {
		$dir = dir($path);
		while (false !== ($filename = $dir->read())) {
			if (mb_substr($filename, 0, 1) != '.' && !($path == DecodeExplorer::$rootpath && ($filename == 'decode' || $filename == 'index.php'))) { //does not start with a period (is not a hidden file) and is not the 'decode' system folder in the rootpath
				$filepath = $path . '/' . $filename;
				if (is_dir($filepath)) {
					$this->folders[] = new Folder($filepath);
				} else { //is_file
					$file = new File($filepath);
					$this->files[] = $file;
					$this->total_size += $file->get_size_in_bytes();
				}
				$this->total_objects++;
			}
		}
		$dir->close();
	}
	
	/**
	 * Sorts the current DirList
	 * @param string $sort_by name/size/date
	 * @param string $sort_in asc/desc
	 */
	private function sort($sort_by, $sort_in) {
		if ($sort_by == 'name' || $sort_by == 'date') { //sort both by name or by date
			$folder_cmp = $files_cmp = $sort_by;
		} else if ($sort_by == 'size') { //sort them differently
			$folder_cmp = 'name'; //no size for folders
			$files_cmp = 'size';
		} else { //sort by name by default
			$folder_cmp = $files_cmp = 'name';
		}
		usort($this->folders, array($this, $folder_cmp . '_cmp'));
		usort($this->files, array($this, $files_cmp . '_cmp'));
		//reverse if needed
		if ($sort_in == 'desc') {
			$this->folders = array_reverse($this->folders);
			$this->files = array_reverse($this->files);
		}
	}
	
	/**
	 * File name sorting comparator
	 * @param File|Folder $o1 File or Folder object
	 * @param File|Folder $o2 File or Folder object
	 * @return int < 0 if $o1->name is less than $o2->name; > 0 if $o1->name is greater than $o2->name, and 0 if they are equal
	 */
	private static function name_cmp($o1, $o2) {
		$name1 = $o1->get_name();
		$name2 = $o2->get_name();
		// use the natural sorting algorithm if both of the strings contain numbers
		return (preg_match('/[0-9]/', $name1) && preg_match('/[0-9]/', $name2)) ? strnatcasecmp($name1, $name2) : strcasecmp($name1, $name2);
	}
	
	/**
	 * File size sorting comparator
	 * @param File $o1 File object
	 * @param File $o2 File object
	 * @return int < 0 if $o1->size is less than $o2->size; > 0 if $o1->size is greater than $o2->size, and 0 if they are equal
	 */
	private static function size_cmp($o1, $o2) {
		$subtract = $o1->get_size_in_bytes() - $o2->get_size_in_bytes();
		//this could be just return $subtract, but that wouldn't work for large filesizes
		if ($subtract < 0) {
			return -1;
		} else if ($subtract > 0) {
			return 1;
		}
		return 0;
	}
	
	/**
	 * File last-modified date comparator
	 * @param File|Folder $o1 File or Folder object
	 * @param File|Folder $o2 File or Folder object
	 * @return int < 0 if $o1->date is less than $o2->date; > 0 if $o1->date is greater than $o2->date, and 0 if they are equal
	 */
	private static function date_cmp($o1, $o2) {
		return strcasecmp($o1->get_date(), $o2->get_date());
	}
	
	/**
	 * Builds the sorting URLs
	 * @param string $sort_by name/size/date
	 * @param string $sort_in asc/desc
	 * @param string $path current path
	 */
	private function build_sorting_urls($sort_by, $sort_in, $path) {
		$path = Helper::format_url(Helper::remove_rootpath($path));
		$name_sort_in = ($sort_by == 'name' && $sort_in == 'asc') ? 'desc' : 'asc';
		$size_sort_in = ($sort_by == 'size' && $sort_in == 'asc') ? 'desc' : 'asc';
		$date_sort_in = ($sort_by == 'date' && $sort_in == 'asc') ? 'desc' : 'asc';
		$this->name_sort_url = "?path=$path&amp;sort_by=name&amp;sort_in=$name_sort_in";
		$this->size_sort_url = "?path=$path&amp;sort_by=size&amp;sort_in=$size_sort_in";
		$this->date_sort_url = "?path=$path&amp;sort_by=date&amp;sort_in=$date_sort_in";
	}
	
	/**
	 * Builds the breadcrumb
	 * @param string $path
	 * @return array the breadcrumb (path and title pairs)
	 */
	private function build_breadcrumb($path) {
		$path = Helper::remove_rootpath($path);
		$path_array = explode('/', $path);
		$breadcrumb = array();
		while (count($path_array) > 0) { //array still has elements left
			$parent_path = Helper::format_url(implode('/', $path_array));
			$parent_name = Helper::trim_name(end($path_array), 25);
			if ($parent_name == '.') {
				$parent_name = DecodeExplorer::$page_title;
			}
			array_unshift($breadcrumb, array('path' => $parent_path, 'name' => $parent_name));
			array_pop($path_array);
		}
		return $breadcrumb;
	}
	
	/**
	 * @return array the breadcrumb (path and title pairs)
	 */
	public function get_breadcrumb() {
		return $this->breadcrumb;
	}
	
	/**
	 * @return string the path to the parent folder
	 */
	public function get_parent_folder() {
		return $this->parent_folder;
	}
	
	/**
	 * @return int formatted total size of the files in this directory
	 */
	public function get_total_size() {
		return $this->total_size;
	}
	
	/**
	 * @return int total number of objects in this directory
	 */
	public function get_total_objects() {
		return $this->total_objects;
	}
	
	/**
	 * @return array Folder objects
	 */
	public function get_folders() {
		return $this->folders;
	}
	
	/**
	 * @return array File objects
	 */
	public function get_files() {
		return $this->files;
	}
	
	/**
	 * @return string URL
	 */
	public function get_name_sort_url() {
		return $this->name_sort_url;
	}
	
	/**
	 * @return string URL
	 */
	public function get_size_sort_url() {
		return $this->size_sort_url;
	}
	
	/**
	 * @return string URL
	 */
	public function get_date_sort_url() {
		return $this->date_sort_url;
	}
	
	/**
	 * @return string name/size/date
	 */
	public function get_sorted_by() {
		return $this->sorted_by;
	}
	
	/**
	 * @return string asc/desc
	 */
	public function get_sorted_in() {
		return $this->sorted_in;
	}
}
?>